package com.school.sports.aspect;

import com.alibaba.fastjson2.JSON;
import com.school.sports.entity.OperationLog;
import com.school.sports.entity.OperationType;
import com.school.sports.service.OperationLogService;
import com.school.sports.util.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 操作日志切面
 * 自动记录带有@OperationLog注解的方法的操作日志
 */
@Aspect
@Component
public class OperationLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);

    @Autowired
    private OperationLogService operationLogService;

    /**
     * 定义切点：所有带有@OperationLog注解的方法
     */
    @Pointcut("@annotation(com.school.sports.annotation.OperationLog)")
    public void operationLogPointcut() {
    }

    /**
     * 环绕通知：记录操作日志
     * @param joinPoint 连接点
     * @return 方法执行结果
     * @throws Throwable 异常
     */
    @Around("operationLogPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取当前时间（开始时间）
        long startTime = System.currentTimeMillis();

        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes != null ? attributes.getRequest() : null;

        // 获取方法信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        com.school.sports.annotation.OperationLog operationLogAnnotation = method.getAnnotation(com.school.sports.annotation.OperationLog.class);

        // 创建操作日志对象
        OperationLog operationLog = new OperationLog();

        // 设置基本信息
        setBasicInfo(operationLog, operationLogAnnotation, request, method);

        // 设置请求信息
        setRequestInfo(operationLog, request, joinPoint, operationLogAnnotation);

        Object result = null;
        Exception exception = null;

        try {
            // 执行目标方法
            result = joinPoint.proceed();

            // 设置响应信息
            setResponseInfo(operationLog, result, operationLogAnnotation);

            // 设置执行状态为成功
            operationLog.setStatus(1);

        } catch (Exception e) {
            // 记录异常信息
            exception = e;
            operationLog.setStatus(0);
            operationLog.setErrorMessage(e.getMessage());

            logger.error("操作执行异常：{}", e.getMessage(), e);
            throw e;
        } finally {
            // 计算响应时间
            long endTime = System.currentTimeMillis();
            operationLog.setResponseTime(endTime - startTime);

            // 异步保存操作日志
            saveOperationLogAsync(operationLog);
        }

        return result;
    }

    /**
     * 设置基本信息
     */
    private void setBasicInfo(OperationLog operationLog, com.school.sports.annotation.OperationLog annotation,
                             HttpServletRequest request, Method method) {
        try {
            // 获取当前用户信息
            String username = SecurityUtils.getCurrentUsername();
            Long userId = SecurityUtils.getCurrentUserId();
            String userRole = SecurityUtils.getCurrentUserRole();

            operationLog.setUserId(userId);
            operationLog.setUsername(username);
            operationLog.setUserRole(userRole);

            // 设置操作信息
            operationLog.setOperationType(annotation.operationType().getCode());
            operationLog.setDescription(annotation.description());
            operationLog.setModule(annotation.module());
            operationLog.setFunction(method.getName());

            // 设置操作时间
            operationLog.setOperationTime(LocalDateTime.now());

        } catch (Exception e) {
            logger.error("设置操作日志基本信息异常：{}", e.getMessage(), e);
        }
    }

    /**
     * 设置请求信息
     */
    private void setRequestInfo(OperationLog operationLog, HttpServletRequest request,
                               ProceedingJoinPoint joinPoint, com.school.sports.annotation.OperationLog annotation) {
        try {
            if (request != null) {
                // 设置请求方法和URL
                operationLog.setRequestMethod(request.getMethod());
                operationLog.setRequestUrl(request.getRequestURI());

                // 设置IP地址
                operationLog.setIpAddress(getIpAddress(request));

                // 设置用户代理
                operationLog.setUserAgent(request.getHeader("User-Agent"));
            }

            // 设置请求参数
            if (annotation.recordParams()) {
                String params = getRequestParams(joinPoint, annotation.sensitiveParams());
                operationLog.setRequestParams(params);
            }

        } catch (Exception e) {
            logger.error("设置操作日志请求信息异常：{}", e.getMessage(), e);
        }
    }

    /**
     * 设置响应信息
     */
    private void setResponseInfo(OperationLog operationLog, Object result, com.school.sports.annotation.OperationLog annotation) {
        try {
            if (annotation.recordResult() && result != null) {
                // 过滤敏感信息
                String responseResult = filterSensitiveData(JSON.toJSONString(result));
                operationLog.setResponseResult(responseResult);
            }
        } catch (Exception e) {
            logger.error("设置操作日志响应信息异常：{}", e.getMessage(), e);
        }
    }

    /**
     * 获取请求参数
     */
    private String getRequestParams(ProceedingJoinPoint joinPoint, String[] sensitiveParams) {
        try {
            Object[] args = joinPoint.getArgs();
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            String[] paramNames = signature.getParameterNames();

            if (paramNames == null || paramNames.length == 0) {
                return JSON.toJSONString(args);
            }

            // 构建参数Map
            Map<String, Object> paramMap = new java.util.HashMap<>();
            for (int i = 0; i < paramNames.length && i < args.length; i++) {
                String paramName = paramNames[i];
                Object paramValue = args[i];

                // 过滤敏感参数
                if (isSensitiveParam(paramName, sensitiveParams)) {
                    paramValue = "***";
                } else if (paramValue instanceof HttpServletRequest ||
                          paramValue instanceof HttpServletResponse) {
                    // 过滤HTTP请求和响应对象
                    paramValue = paramValue.getClass().getSimpleName() + "@" + Integer.toHexString(paramValue.hashCode());
                }

                paramMap.put(paramName, paramValue);
            }

            return JSON.toJSONString(paramMap);
        } catch (Exception e) {
            logger.error("获取请求参数异常：{}", e.getMessage(), e);
            return "获取参数失败";
        }
    }

    /**
     * 判断是否为敏感参数
     */
    private boolean isSensitiveParam(String paramName, String[] sensitiveParams) {
        if (sensitiveParams == null || paramName == null) {
            return false;
        }
        return Arrays.asList(sensitiveParams).contains(paramName);
    }

    /**
     * 过滤敏感数据
     */
    private String filterSensitiveData(String jsonStr) {
        if (jsonStr == null || jsonStr.isEmpty()) {
            return jsonStr;
        }

        try {
            // 简单的敏感信息过滤
            // 实际项目中可以使用更复杂的正则表达式或JSON解析
            String result = jsonStr;

            // 过滤密码相关字段
            result = result.replaceAll("\"password[^\"]*\":\"[^\"]*\"", "\"password\":\"***\"");
            result = result.replaceAll("\"passwordHash[^\"]*\":\"[^\"]*\"", "\"passwordHash\":\"***\"");
            result = result.replaceAll("\"token[^\"]*\":\"[^\"]*\"", "\"token\":\"***\"");

            return result;
        } catch (Exception e) {
            logger.error("过滤敏感数据异常：{}", e.getMessage(), e);
            return jsonStr;
        }
    }

    /**
     * 获取客户端IP地址
     */
    private String getIpAddress(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }

        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        // 如果是多个IP，取第一个
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }

        return ip;
    }

    /**
     * 异步保存操作日志
     */
    private void saveOperationLogAsync(OperationLog operationLog) {
        try {
            // 使用异步方式保存日志，避免影响主业务流程
            new Thread(() -> {
                try {
                    operationLogService.saveOperationLog(operationLog);
                    logger.debug("操作日志保存成功：{}", operationLog.getDescription());
                } catch (Exception e) {
                    logger.error("异步保存操作日志异常：{}", e.getMessage(), e);
                }
            }).start();
        } catch (Exception e) {
            logger.error("启动异步保存操作日志线程异常：{}", e.getMessage(), e);
        }
    }
}